﻿using System.Collections.Generic;

namespace Aeon.Emulator.DebugSupport
{
    /// <summary>
    /// Contains methods for formatting a memory address to text.
    /// </summary>
    internal static class AddressFormatter
    {
        #region Constructors
        /// <summary>
        /// Initializes static fields on the AddressFormatter class.
        /// </summary>
        static AddressFormatter()
        {
            effectiveAddresses.Add(CodeMemoryBase.BP, "bp");
            effectiveAddresses.Add(CodeMemoryBase.BP_plus_DI, "bp+di");
            effectiveAddresses.Add(CodeMemoryBase.BP_plus_SI, "bp+si");
            effectiveAddresses.Add(CodeMemoryBase.BX, "bx");
            effectiveAddresses.Add(CodeMemoryBase.BX_plus_DI, "bx+di");
            effectiveAddresses.Add(CodeMemoryBase.BX_plus_SI, "bx+si");
            effectiveAddresses.Add(CodeMemoryBase.DI, "di");
            effectiveAddresses.Add(CodeMemoryBase.EAX, "eax");
            effectiveAddresses.Add(CodeMemoryBase.EBX, "ebx");
            effectiveAddresses.Add(CodeMemoryBase.EBP, "ebp");
            effectiveAddresses.Add(CodeMemoryBase.ECX, "ecx");
            effectiveAddresses.Add(CodeMemoryBase.EDI, "edi");
            effectiveAddresses.Add(CodeMemoryBase.EDX, "edx");
            effectiveAddresses.Add(CodeMemoryBase.ESI, "esi");
            effectiveAddresses.Add(CodeMemoryBase.SI, "si");

            sibRegisters.Add(CodeSibRegister.EAX, "eax");
            sibRegisters.Add(CodeSibRegister.EBP, "ebp");
            sibRegisters.Add(CodeSibRegister.EBX, "ebx");
            sibRegisters.Add(CodeSibRegister.ECX, "ecx");
            sibRegisters.Add(CodeSibRegister.EDI, "edi");
            sibRegisters.Add(CodeSibRegister.EDX, "edx");
            sibRegisters.Add(CodeSibRegister.ESI, "esi");
            sibRegisters.Add(CodeSibRegister.ESP, "esp");
        }
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Returns a string representation of a memory address.
        /// </summary>
        /// <param name="memoryOperand">Memory address operand to be converted to a string.</param>
        /// <param name="prefixes">Current instruction prefixes in effect.</param>
        /// <returns>String representation of the memory address.</returns>
        public static string Format(CodeOperand memoryOperand, PrefixState prefixes)
        {
            string prefix = GetSizePrefix(memoryOperand.OperandSize);

            if(memoryOperand.EffectiveAddress == CodeMemoryBase.SIB)
                return prefix + FormatSib(memoryOperand);

            string register;
            effectiveAddresses.TryGetValue(memoryOperand.EffectiveAddress, out register);

            string segmentOverride = FormatSegment(prefixes);

            if(register == null)
                return string.Format("{0} {1}[{2:X4}]", prefix, segmentOverride, memoryOperand.ImmediateValue);
            else if(memoryOperand.ImmediateValue == 0)
                return string.Format("{0} {1}[{2}]", prefix, segmentOverride, register);
            else
                return string.Format("{0} {1}[{2}+{3:X4}]", prefix, segmentOverride, register, memoryOperand.ImmediateValue);
        }
        #endregion

        #region Private Static Methods
        private static string FormatSib(CodeOperand sibOperand)
        {
            string baseRegister;
            sibRegisters.TryGetValue(sibOperand.Base, out baseRegister);
            string scaleIndex = FormatScaleIndex(sibOperand.Scale, sibOperand.Index);

            if(baseRegister == null)
                return string.Format("[{0:X8}]{1}", sibOperand.ImmediateValue, scaleIndex);
            else
            {
                if(sibOperand.ImmediateValue == 0)
                    return string.Format("[{0}]{1}", baseRegister, scaleIndex);
                else
                    return string.Format("[{0}+{1:X8}]{2}", baseRegister, sibOperand.ImmediateValue, scaleIndex);
            }
        }
        /// <summary>
        /// Returns a string containing the scale and index part of a 32-bit address.
        /// </summary>
        /// <param name="scale">Scaling factor.</param>
        /// <param name="index">Index register.</param>
        /// <returns>String containing the scale and index part of a 32-bit address.</returns>
        private static string FormatScaleIndex(int scale, CodeSibRegister index)
        {
            string indexRegister;
            sibRegisters.TryGetValue(index, out indexRegister);

            if(indexRegister == null)
                return string.Empty;

            if(scale == 1)
                return string.Format("[{0}]", indexRegister);
            else
                return string.Format("[{0}*{1}]", indexRegister, scale);
        }
        /// <summary>
        /// Returns a string describing the size of a target of a pointer.
        /// </summary>
        /// <param name="size">Size of the pointer.</param>
        /// <returns>String describing the pointer's target size.</returns>
        private static string GetSizePrefix(CodeOperandSize size)
        {
            switch(size)
            {
            case CodeOperandSize.Byte:
                return "byte ptr";

            case CodeOperandSize.Word:
                return "word ptr";

            case CodeOperandSize.DoubleWord:
                return "dword ptr";

            default:
                return string.Empty;
            }
        }
        /// <summary>
        /// Returns a string representation of the current segment override.
        /// </summary>
        /// <param name="prefixes">Current state of instruction prefixes.</param>
        /// <returns>String representation of the segment override.</returns>
        private static string FormatSegment(PrefixState prefixes)
        {
            switch(prefixes & SegmentOverrideMask)
            {
            case PrefixState.CS:
                return "cs:";

            case PrefixState.DS:
                return "ds:";

            case PrefixState.ES:
                return "es:";

            case PrefixState.FS:
                return "fs:";

            case PrefixState.GS:
                return "gs:";

            case PrefixState.SS:
                return "ss:";

            default:
                return string.Empty;
            }
        }
        #endregion

        #region Public Constants
        /// <summary>
        /// Specifies the prefix override flags.
        /// </summary>
        public const PrefixState SegmentOverrideMask = PrefixState.CS | PrefixState.DS | PrefixState.ES | PrefixState.FS | PrefixState.GS | PrefixState.SS;
        #endregion

        #region Private Static Fields
        private static readonly Dictionary<CodeMemoryBase, string> effectiveAddresses = new Dictionary<CodeMemoryBase, string>();
        private static readonly Dictionary<CodeSibRegister, string> sibRegisters = new Dictionary<CodeSibRegister, string>();
        #endregion
    }
}
